using System;
using System.Text;
using System.IO;
using System.Collections.Generic;
using System.Threading;
using System.ComponentModel;
using System.Security.Cryptography;
using System.Linq;

namespace AdaNNTP
{
	public class NewsgroupService
	{
		public List<Task> downloadTasks = new List<Task>();
		public readonly List<NNTPServer> availableServers = new List<NNTPServer>(Ada.Protocol.Config.Instance.knownServers);
		
		public delegate void OnTaskCompleteDelegate(string title, string owner, long size);
		public event OnTaskCompleteDelegate OnTaskCompleteEvent;
		
		internal List<TaggedThread> downloadThreads;

		internal Dictionary<string, int> serverConnectionCount = new Dictionary<string, int>();

        internal Dictionary<string, bool> fileAssemblyAssigned = new Dictionary<string, bool>();

        internal ReaderWriterLockSlim accessLock = new ReaderWriterLockSlim();
		
		public bool Active  {
			get;
			private set;
		}

		
		public string outputRootPath {
			get;
			set;
		}
		
		#region Singleton Pattern
		private static readonly NewsgroupService instance = new NewsgroupService();
		
		//Force .net compiler to not apply beforefieldinit. That would be very naughty indeed.
		static NewsgroupService()
		{
		}
		
		NewsgroupService()
		{	
		}
		
		public static NewsgroupService Instance
		{
			get
			{
				return instance;
			}
		}
		#endregion
		
		public bool EnqueueNZB(Stream nzbStream, string title)
		{
			Task newDownload = new Task() { Title = title };

			try
			{
				newDownload.DecodeNZB(nzbStream);
			}
			catch
			{
				return false;
			}
			
			#region File Download Complete delegate
			foreach(File queuedFile in newDownload.Files)
			{
				queuedFile.Filename = queuedFile.Subject;
				queuedFile.OnFileDownloadComplete += delegate() {
                    lock (fileAssemblyAssigned)
                    {
                        if (fileAssemblyAssigned.ContainsKey(queuedFile.Filename + queuedFile.Date.ToShortTimeString()))
                        {
                            if (fileAssemblyAssigned[queuedFile.Filename + queuedFile.Date.ToShortTimeString()] == true)
                            {
                                Console.WriteLine("fileAssemblyDebug was TRUE for " + queuedFile.Filename);
                                return;
                            }
                            else
                            {
                                fileAssemblyAssigned[queuedFile.Filename + queuedFile.Date.ToShortTimeString()] = true;
                                StringBuilder sb = new StringBuilder();

                                var segmentStrings = from Segment segment in queuedFile.Segments
                                                     orderby segment.Number
                                                     select segment.Contents;

                                foreach (string segmentContent in segmentStrings)
                                {
                                    Console.WriteLine(segmentContent);
                                    sb.Append(segmentContent);
                                }

                                queuedFile.Contents = sb.ToString();

                                FileAssembler assembler = new FileAssembler()
                                {
                                    encodedFileContents = queuedFile.Contents,
                                    outputPath = outputRootPath,
                                };

                                Thread assemblyThread = new Thread(assembler.Assemble);
                                assemblyThread.Start();
                            }

                        }
                        else
                        {
                            fileAssemblyAssigned[queuedFile.Filename + queuedFile.Date.ToShortTimeString()] = true;
                            StringBuilder sb = new StringBuilder();

                            var segmentStrings = from Segment segment in queuedFile.Segments
                                                 orderby segment.Number
                                                 select segment.Contents;

                            foreach (string segmentContent in segmentStrings)
                            {
                                Console.WriteLine(segmentContent);
                                sb.Append(segmentContent);
                            }

                            queuedFile.Contents = sb.ToString();

                            FileAssembler assembler = new FileAssembler()
                            {
                                encodedFileContents = queuedFile.Contents,
                                outputPath = outputRootPath,
                            };

                            Thread assemblyThread = new Thread(assembler.Assemble);
                            assemblyThread.Start();
                        }
                    }

                    
                    

                    //Force the task completion to update
                    newDownload.GetProgress();
                    
				};
			}
			
			#endregion

            
			#region Segment Download Complete Delegate
			var segmentsToBeDownloaded = from File newFile in newDownload.Files
										 from Segment newSegment in newFile.Segments
										 select newSegment;
			
			foreach (Segment currentSegment in segmentsToBeDownloaded)
			{
				
				currentSegment.OnSegmentDownloadComplete += delegate() {
					var hostFile = (from File newFile in newDownload.Files
									from Segment newSegment in newFile.Segments
									where newSegment.MessageID == currentSegment.MessageID
									select newFile).Single();
					
					//On each segment completion, refresh the file's progress
					//To trigger the automatic decode & write-out.
					hostFile.GetProgress();

                    availableServers.Sort();
                    if (serverConnectionCount[availableServers.First().serverAddress] < (availableServers.First().MaxConnectionCount))
                    {
                        NewSegmentThread();
                    }
				};
			}
			#endregion
            
			newDownload.OnTaskDownloadComplete += delegate()
			{
				downloadTasks.Remove(newDownload);
                foreach (File file in newDownload.Files)
                {
                    fileAssemblyAssigned[file.Filename + file.Date.ToShortTimeString()] = false;
                }
			};

			downloadTasks.Add(newDownload);
			//GENTLEMEN, START YOUR ENGINES!
			//try
			//{
				Start();
			//}
			//catch
			//{
			//    return false;
			//}

			return true;
		}
		
		public void EnqueueNZB(Stream nzbStream)
		{
			EnqueueNZB(nzbStream, null);
		}
		
		public void Start()
		{
			Active = true;

			var totalThreadCount = (from NNTPServer server in availableServers
									select server).Sum(x => x.MaxConnectionCount);

            downloadThreads = new List<TaggedThread>(totalThreadCount);
			
			foreach(NNTPServer server in availableServers)
			{
				if (!serverConnectionCount.ContainsKey(server.serverAddress))
					serverConnectionCount[server.serverAddress] = 0;

				while (serverConnectionCount[server.serverAddress] < (server.MaxConnectionCount))
				{
                    NewSegmentThread();
				}
			}			
		}
		
        private void NewSegmentThread()
        {
            Segment segmentToDownload;

            try
            {
                segmentToDownload = downloadTasks.First().Files.First(x => x.Completed == false).Segments.
                    Where(n => n.Completed == false).
                    Where(x => x.Active == false).
                    First(y => y.Contents == null);
            }
            catch
            {
                return;
            }
            SegmentDownloader downloader = new SegmentDownloader(segmentToDownload);
            //downloader.segmentToDownload = downloadTasks.First().Files.First(x => x.Completed == false).Segments.Where(n => n.Completed == false).First(x => x.Active == false);
            //availableServers.Sort();
            downloader.targetServer = availableServers.First();

            downloader.OnDownloadComplete += delegate(string Body)
            {
                lock (serverConnectionCount)
                {
                    serverConnectionCount[downloader.targetServer.serverAddress]--;
                }
                downloader.Dispose(true);
            };

            downloader.OnDownloadFailed += delegate(Segment segmentThatFailed)
            {
                lock (serverConnectionCount)
                {
                    serverConnectionCount[downloader.targetServer.serverAddress]--;
                }
                downloader.Dispose(true);
            };

            //Create a new thread for the download, and set it running.
            //Note that as a result of the event system, we can have a new thread to deal with each post-download event
            //This is effectively the start of a flow cascade, where each completed segment causes a check in the total file completion
            //Once completed, the file calls an event which causes decode & assembly, and causes a check on task completion
            //If that task completion is true, then the entire task is removed from the queue and sent to history.

            Guid threadName = Guid.NewGuid();
            downloadThreads.Add(new TaggedThread() { downloadThread = new Thread(downloader.SegmentDownload), ID = threadName });
            downloadThreads.Where((x) => x.ID == threadName).Single().downloadThread.Start();
            lock (serverConnectionCount)
            {
                serverConnectionCount[downloader.targetServer.serverAddress]++;

                Console.WriteLine("serverConnectionCount: " + serverConnectionCount[downloader.targetServer.serverAddress].ToString());
            }
        }

		public void Stop()
		{
			//One flag to rule them all.
			Active = false;	
		}
		
		
	}

    internal struct TaggedThread
    {
        internal Thread downloadThread;
        internal Guid ID;
    }
}

